use serde::{Deserialize, Serialize};
use std::fs;
use std::io::Read;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FsEntry {
    pub name: String,
    pub path: String,
    pub is_dir: bool,
    pub size: u64,
    pub modified_ms: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FsStat {
    pub path: String,
    pub is_dir: bool,
    pub size: u64,
    pub modified_ms: u64,
}

fn to_unix_ms(st: SystemTime) -> u64 {
    st.duration_since(UNIX_EPOCH)
        .unwrap_or_default()
        .as_millis()
        .saturating_sub(0) as u64
}

fn canonical_string<P: AsRef<Path>>(p: P) -> String {
  #[cfg(target_os = "windows")]
  {
    use std::path::PathBuf;
    // 使用 dunce::canonicalize 以避免返回带有 \\?\ 的扩展长度前缀
    let pbuf = dunce::canonicalize(p.as_ref()).unwrap_or_else(|_| PathBuf::from(p.as_ref()));
    let mut s = pbuf.to_string_lossy().into_owned();
    // 额外保险：剥离可能残留的前缀
    if let Some(rest) = s.strip_prefix(r"\\?\UNC\") {
      // \\?\UNC\server\share\path -> \\server\share\path
      s = format!(r"\\{}", rest);
    } else if let Some(rest) = s.strip_prefix(r"\\?\") {
      // \\?\C:\path -> C:\path
      s = rest.to_string();
    }
    s
  }
  #[cfg(not(target_os = "windows"))]
  {
    use std::path::PathBuf;
    fs::canonicalize(p)
      .ok()
      .unwrap_or_else(|| PathBuf::from(""))
      .to_string_lossy()
      .into_owned()
  }
}

#[tauri::command]
pub fn fs_read_text(path: String) -> Result<String, String> {
    let p = PathBuf::from(&path);
    let mut f = fs::File::open(&p).map_err(|e| e.to_string())?;
    let mut s = String::new();
    f.read_to_string(&mut s).map_err(|e| e.to_string())?;
    Ok(s)
}

#[tauri::command]
pub fn fs_write_text(path: String, content: String, create_dirs: Option<bool>) -> Result<(), String> {
    let p = PathBuf::from(&path);
    if create_dirs.unwrap_or(true) {
        if let Some(parent) = p.parent() { let _ = fs::create_dir_all(parent); }
    }
    fs::write(&p, content.as_bytes()).map_err(|e| e.to_string())
}

#[tauri::command]
pub fn fs_read_dir(path: String) -> Result<Vec<FsEntry>, String> {
    let p = PathBuf::from(&path);
    let mut out: Vec<FsEntry> = Vec::new();
    let rd = fs::read_dir(&p).map_err(|e| e.to_string())?;
    for ent in rd {
        let ent = ent.map_err(|e| e.to_string())?;
        let meta = ent.metadata().map_err(|e| e.to_string())?;
        let name = ent
            .file_name()
            .to_string_lossy()
            .into_owned();
        let full = ent.path();
        let modified = meta.modified().unwrap_or(SystemTime::UNIX_EPOCH);
        out.push(FsEntry {
            name,
            path: canonical_string(&full),
            is_dir: meta.is_dir(),
            size: if meta.is_file() { meta.len() } else { 0 },
            modified_ms: to_unix_ms(modified),
        });
    }
    // stable sort: folders first then name asc
    out.sort_by(|a, b| match b.is_dir.cmp(&a.is_dir) { // true before false
        std::cmp::Ordering::Equal => a.name.to_lowercase().cmp(&b.name.to_lowercase()),
        o => o,
    });
    Ok(out)
}

#[tauri::command]
pub fn fs_mkdir(path: String, recursive: Option<bool>) -> Result<(), String> {
    let p = PathBuf::from(&path);
    if recursive.unwrap_or(true) { fs::create_dir_all(&p) } else { fs::create_dir(&p) }
        .map_err(|e| e.to_string())
}

#[tauri::command]
pub fn fs_delete(path: String, recursive: Option<bool>) -> Result<(), String> {
    let p = PathBuf::from(&path);
    let meta = fs::metadata(&p).map_err(|e| e.to_string())?;
    if meta.is_dir() {
        if recursive.unwrap_or(true) { fs::remove_dir_all(&p) } else { fs::remove_dir(&p) }
            .map_err(|e| e.to_string())
    } else {
        fs::remove_file(&p).map_err(|e| e.to_string())
    }
}

#[tauri::command]
pub fn fs_rename(from: String, to: String) -> Result<(), String> {
    let from_p = PathBuf::from(&from);
    if let Some(parent) = Path::new(&to).parent() { let _ = fs::create_dir_all(parent); }
    fs::rename(&from_p, &to).map_err(|e| e.to_string())
}

#[tauri::command]
pub fn fs_stat(path: String) -> Result<FsStat, String> {
    let p = PathBuf::from(&path);
    let meta = fs::metadata(&p).map_err(|e| e.to_string())?;
    let modified = meta.modified().unwrap_or(SystemTime::UNIX_EPOCH);
    Ok(FsStat {
        path: canonical_string(&p),
        is_dir: meta.is_dir(),
        size: if meta.is_file() { meta.len() } else { 0 },
        modified_ms: to_unix_ms(modified),
    })
}


